正文：
1、先明确一个C语言运算符的优先级关系：()>[ ]> *；

2、然后再来看: int (* p)[ 5]; 根据优先级，先看( )内，p是一个指针，理解到这里，即不管p后面怎么样，p就是一个指针，后面的

[ ]又说明了 p是一个什么样的指针呢，p是一个指向数组的指针，然后再加上前面的int，所以最后，p是一个指向 int 类型的数组的一个指针。所以这种格式叫 数组指针。

3、 再来看: int *p[ 5]:  根据优先级，先看[ ]，p是一个数组，后面不管怎么样，p就是一个数组，再结合 *，数组里面是什么元素呢，数组里面的元素是指针，再加上int， p是一个数组，数组中的元素全是int类型的指针。这种格式就叫 数组指针，也可以写成：int *(p[ 5 ]);


数组指针
    int *p[5];
    int (*p)[5];
1.1区分
数组指针的意思即为通过指针引用数组，p先和*结合，说明了p是一个指针变量，
指向一个大小为5的数组。所以，int (*p)[5]即为一个数组指针。
int *p[5]则是一个大小为5且存放整型指针的数组。
注意：数组名并不代表整个数组，只是代表数组首元素的地址，因此上面两个语句是一样的。

1.2数组元素的指针

1.2.1定义
指针变量既然可以指向变量，同样的，也能指向数组元素，因此，数组元素的指针就是数组元素的地址。	
写法为：
    int *p=arr;
    int *p=&a[0];
1.2.2运算
由于指针指向的是一个地址，因此数组指针也同样可以进行相关运算；
例如指针的加减可以实现指针指向数组上一个或者下一个元素的功能。
这边需要说明，数组指针中进行乘法和除法是没有意义的。
在定义指针变量的时候需要定义类型，如果指针p指向了一个数组中的一个元素，
那么p+1并不是将地址加上1，而是系统判定类型之后加上一个数组元素所占用的字节数（即为p+1*d）。
1.2.3通过指针引用数组元素
	ex：#include<stdio.h>
		int main()
		{
			typedef int arrNum_10[10];
			arrNum_10 arr = {0,1,2,3,4,5,6,7,8,9};
			int *p_arr,i;
			p_arr = arr;
			for(i = 0;i<10;i++)
			{
				(*p_arr)++;
				p_arr++;
			}
			p_arr = arr;
			for(i = 0;i<10;i++)
			{
				printf("arr[%d] = %d\n",i,arr[i]);
				printf("p_arr[%d] = %d\n",i,*(p_arr+i));
			}
		}

通过指针引用多维数组
1.3多维数组元素的地址
二维数组为例，首先需要明确一点的是二维数组的首元素地址并非一个单个元素，而是首行的地址
相关地址的表示方式：
表示形式 							含义
a     						二维数组名，指向a[0]
a[0], *(a+0), *a 			0行0列元素地址
a+1, &a[1] 					1行首地址
a[1], *(a+1) 				a[1][0]的地址
a[1]+2, *(a+1)+2, &a[1][2] 	a[1][2]的地址
*(a[1]+2), *(*(a+1)+2), a[1][2] 	a[1][2]的值
	ex：#include<stdio.h>
		int main()
		{
			typedef int arrNum_3_4[3][4];
			arrNum_3_4 arr = {0,1,2,3,4,5,6,7,8,9,10,11};
			int (*p_arr)[4],i,j;
			p_arr = arr;
			for(i =0; i <3;i++)
			{
				for(j = 0;j<4;j++)
				{
					++*(*(p_arr+i)+j);
				//	(*(p_arr+i)+j)++;
				}
			}
			printf("arr = %p\n",arr);arr;
			printf("p_arr = %p\n",p_arr); 
			printf("&arr = %p\n",&arr);
			printf("arr+1 = %p\n",(arr+1));
			printf("p_arr+1 = %p\n",(p_arr+1));
			printf("arr[1][0] = %p\n",&arr[1][0]);
			printf("(*(p_arr+1)+0) = %p\n",(*(p_arr+1)+0));
			printf("arr[1][1] = %p\n",&arr[1][1]);
			printf("(*(p_arr+1)+1) = %p\n",(*(p_arr+1)+1));
			printf("arr[1][2] = %p\n",&arr[1][2]);
			printf("(*(p_arr+1)+2) = %p\n",(*(p_arr+1)+2));
			p_arr = arr;
			for(i =0; i <3;i++)
			{
				for(j = 0;j<4;j++)
				{
					printf("p_arr[%d][%d] = %d\t\t",i,j,*(*(p_arr+i)+j));
				}
				printf("\n");
			}
		}
数组指针作函数参数
以下是几种传参数方式：
					void test()
					{
					 int arr[3][5] = {0};
					 print(arr);
					}
					void print(int arr[3][5])
					{}
					void print(int arr[][5])
					{}
					void print(int **arr)
					{}
					void print(int (*arr)[5])
					{}


指针数组
2.1定义：int *p[5];
这就是一个最简单的指针数组。因此我们可以得出指针数组的定义。
指针数组：一个数组的元素均为指针类型数据，称为指针数组。
指针数组（也就是元素为指针类型的数组）常常作为二维数组的一种便捷替代方式。
一般情况下，这种数组中的指针会指向动态分配的内存区域。
ex：如果需要处理字符串，可以将它们存储在一个二维数组中，
	该数组行空间大小必须足以存储下可能出现的最长字符串：
	#define ARRAY_LEN 100
    #define STRLEN_MAX 256
    char myStrings[ARRAY_LEN][STRLEN_MAX] =
    { // 墨菲定律的几条推论：
    “会出错的事，总会出错。”
    “世上没有绝对正确的事情。”
    “每个解决办法都会衍生出新的问题。”
    };
	然而，这个方式造成内存浪费，25600 字节中只有一小部分被实际使用到。
	一方面，短字符串会让大部分的行是空的；另一个方面，有些行根本没有用到，
	但却得为它预留内存。

	一个简单的解决方案是，使用指针数组，让指针指向对象（在此处的对象就是字符串），
	然后只给实际存在的对象分配内存（未用到的数组元素则是空指针）。
	#define ARRAY_LEN 100
    char *myStrPtr[ARRAY_LEN] =        // char指针的数组
    { // 墨菲定律的几条推论：
    “会出错的事，总会出错。”
    “世上没有绝对正确的事情。”
    “每个解决办法都会衍生出新的问题。”
    };
	#include<stdio.h>
	#define ARRAY_LEN 100
	int main()
	{
		typedef char arrNum_100[ARRAY_LEN][ARRAY_LEN];
		//int i = 0;
		arrNum_100 arr;
		arr[ARRAY_LEN][ARRAY_LEN] = {
				 “会出错的事，总会出错。”
				“世上没有绝对正确的事情。”
				“每个解决办法都会衍生出新的问题。”
		}
		while(arr != NULL){
			printf("*(arr) = %s\n",*(arr));
			arr++;
		}
	}
	ex:
		#include<stdio.h>
		#include<stdlib.h>
		#include<string.h>
		#include<malloc.h>
		#include<error.h>
		#define ARRAY_LEN 100
		typedef char* arrNum_100[ARRAY_LEN];
		typedef char arrNum_100_100[ARRAY_LEN][ARRAY_LEN];
		int main()
		{		
			int i = 0;
			arrNum_100 arr1;
			arrNum_100_100 arr2 = {"会出错的事,总会出错","世上没有绝对正确的事情","每个解决办法都会衍生出新的问题"};
			while(arr2[i][0] != '\0'){
				arr1[i] = (char*)malloc(sizeof(char)*strlen(arr2[i]));
				if(arr1[i] == NULL){
					perror("error!");
					system(pause);
				}
				strcpy(arr1[i],arr2[i]);
				printf("arr1[%d] = %s\n",i,arr1[i]);
				printf("&arr1[%d] = %p\n",i,&arr1[i]);
				i++;
			//	free(arr1[i]);
			}
			return 0;
		}
	ex:分析以下程序，输出其运行结果。
		#include<stdio.h>
		int main (void)
		{
			char c[3][5] = {"Apple","Orange","Pear"};
			int i;
			for(i=0;i<3;i++)
				printf ("%s\n",c[i]);
			return 0;
		}
	c[0] 为 c[0] 行的首地址，即 &c[0][0]。
    printf ("%s\n",c[0]); //输出AppleOrangPear
    printf ("%s\n",c[1]); //输出OrangPear
    printf ("%s\n",c[2]); // Pear
	运行结果为：
	AppleOrangPear
	OrangPear
	Pear

指针的安全问题
	ex1：char s=‘a’;  
		int *ptr;  
		ptr=(int *)&s;  
		*ptr=1298；
	指针ptr 是一个int *类型的指针，它指向的类型是int。
	它指向的地址就是s 的首地址。在32 位程序中，
	s 占一个字节，int 类型占四个字节。最后一条语句不但改变了s 所占的一个字节，
	还把和s 相临的高地址方向的三个字节也改变了。这三个字节是干什么的？
	只有编译程序知道，而写程序的人是不太可能知道的。
	也许这三个字节里存储了非常重要的数据，也许这三个字节里正好是程序的一条代码，
	而由于你对指针的马虎应用，这三个字节的值被改变了！这会造成崩溃性的错误。
	ex2：char a;  
		int *ptr=&a;  
		ptr++;  
		*ptr=115;
	该例子完全可以通过编译，并能执行。但是看到没有？
	第3 句对指针ptr 进行自加1 运算后，ptr 指向了和整形变量a 相邻的高地址方向的一块存储区。
	这块存储区里是什么？我们不知道。有可能它是一个非常重要的数据，甚至可能是一条代码。
	而第4 句竟然往这片存储区里写入一个数据！这是严重的错误。所以在使用指针时，
	程序员心里必须非常清楚：我的指针究竟指向了哪里。在用指针访问数组的时候，
	也要注意不要超出数组的低端和高端界限，否则也会造成类似的错误。
	
	在指针的强制类型转换：ptr1=(TYPE *)ptr2 中，如果sizeof(ptr2的类型)大于sizeof(ptr1 的类型)，
	那么在使用指针ptr1 来访问ptr2所指向的存储区时是安全的。
	如果sizeof(ptr2 的类型) 小于sizeof(ptr1 的类型)，
	那么在使用指针ptr1 来访问ptr2 所指向的存储区时是不安全的。
	至于为什么，看ex1
指针类型转换
	当我们初始化一个指针或给一个指针赋值时，赋值号的左边是一个指针，赋值号的右边是一个指针表达式。
	在我们前面所举的例子中，绝大多数情况下，指针的类型和指针表达式的类型是一样的，
	指针所指向的类型和指针表达式所指向的类型是一样的。
	ex1:float f=12.3;  
		float fptr=&f;  
		int p;
	在上面的例子中，假如我们想让指针p 指向实数f，应该怎么办？
	是用下面的语句吗？
	p=&f;
	不对。因为指针p 的类型是int ，它指向的类型是int。表达式&f 的结果是一个指针，
	指针的类型是float ,它指向的类型是float。	
	p=(int)&f;
	
	
	
2.2指向指针数据的指针

main()
{
	static int a[5]={1,3,5,7,9};
	int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};//指针数组存放数组a内容的地址
	int **p,i;
	p=num;//数组a的地址存放在指向指针的指针变量p的地址上
	for(i=0;i<5;i++)
	{
		printf("%d\t",**p);
		p++;
	}//**p要双重解引用，才能读取最上面的值
}
/**此程序段表示首先通过指针数组去获取二维数组的每行首地址
	在通过二级指针指向指针数组，指向指针的指针用来索引数组的每个元素
**/
#include<stdio.h>
#include<stdlib.h>
#include<error.h>
#include<malloc.h>

#define ARRAY_LEN 3
#define ture 1
#define false 0
typedef int s32;
typedef unsigned char bool;
typedef int  arrNum_line_row[ARRAY_LEN][ARRAY_LEN];
typedef int* arrNum_ARRAY_LEN[ARRAY_LEN];
typedef int** OPINET;

bool print(const bool num)
{
	if(num == 0){
		return false;
	}
	return ture;
}

int main(int argc,char *argv[])
{
	s32 erg_arr_line,erg_arr_row;//遍历数组的行列
	arrNum_line_row arr = {1,2,3,4,5,6,7,8,9};
	arrNum_ARRAY_LEN opint_arr;
	OPINET p_arr;
	for(erg_arr_line = 0;erg_arr_line < ARRAY_LEN;erg_arr_line++)
	{
		opint_arr[erg_arr_line ] = (int *)malloc(sizeof(int)*ARRAY_LEN);
		if(opint_arr == NULL){
			perror("error!");
			return false;
		}
		opint_arr[erg_arr_line ] = arr[erg_arr_line];	
	}
	p_arr = opint_arr;
	for(erg_arr_line = 0;erg_arr_line<ARRAY_LEN;erg_arr_line++)
	{
		for(erg_arr_row = 0;erg_arr_row<ARRAY_LEN;erg_arr_row++)
		{
			printf("%d\t",*(*(p_arr+erg_arr_line)+erg_arr_row));
		}
		printf("\n") ;
	}
	return false;
}























